ts-lib-crypto 
The waves protocol is a set of rules named consensus by which nodes reach an agreement on the network, and format which nodes use to communicate with each other. It based on several well described hash and crypto algorithms and has predefined set of entries to operate on network. This library contains all algorithm implementations like signature verification and protocol entries like address used in waves protocol. Also it contains utility methods and format converters to help 3rd party developers.
Agenda
Installation
npm install @waves/ts-lib-crypto
Import styles
The is several ways of doing things when using ts-lib-crypto.
You can import functions strait-forward:
import { address } from '@waves/ts-lib-crypto'
address('my secret seed')
Or you can use a crypto constructor function:
import { crypto } from '@waves/ts-lib-crypto'
const { address } = crypto()
address('my secret seed')
The second approach gives you more flexibility, using this approach you are able to embed the seed and use all seed-dependant functions without seed parameter:
import { crypto } from '@waves/ts-lib-crypto'
const { address } = crypto({seed: 'my secret seed'})
address()
Inputs
ts-lib-crypto is even more flexible. Any function argument that represents binary data or seed could be passed in several ways. Let's take a look on the following example:
import { address } from '@waves/ts-lib-crypto'
const seedString = 'uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine'
const seedBytesAsArray = [117, 110, 99, 108, 101, 32, 112, 117, 115, 104, 32, 104, 117, 109, 97, 110, 32, 98, 117, 115, 32, 101, 99, 104, 111, 32, 100, 114, 97, 115, 116, 105, 99, 32, 103, 97, 114, 100, 101, 110, 32, 106, 111, 107, 101, 32, 115, 97, 110, 100, 32, 119, 97, 114, 102, 97, 114, 101, 32, 115, 101, 110, 116, 101, 110, 99, 101, 32, 102, 111, 115, 115, 105, 108, 32, 116, 105, 116, 108, 101, 32, 99, 111, 108, 111, 114, 32, 99, 111, 109, 98, 105, 110, 101]
const seedBytesAsUintArray = Uint8Array.from(seedBytesAsArray)
address(seedString)
address(seedBytesAsArray)
address(seedBytesAsUintArray)
As you can see seed parameter is treated the same way for number[] or Uint8Array.
When you pass binary data is could be represented as number[] or Uint8Array or even base58:
import { address, randomSeed, sha256 } from '@waves/ts-lib-crypto'
const seed = randomSeed()
const addressBase58 = address(seed)
sha256(addressBase58)
Here we got sha256 hash from address bytes represented as base58 (3P9KR33QyXwfTXv8kKtNGZYtgKk3RXSUk36).
Be aware that sha256 value is not based on "3P9KR33QyXwfTXv8kKtNGZYtgKk3RXSUk36" string itself, this value was treated as a binary data and base58Decode was applied.
Outputs
As you've noticed from the previous section address() output is base58 string like:
By default functions from the following list output base58 string as a result,
no matter what import-style you choose:
keyPair
publicKey
privateKey
address
sharedKey
signBytes
If you prefer binary output, you can alter this behaviour and make those functions to return UInt8Array instead.
When using inline import style:
import { address } from '@waves/ts-lib-crypto/bytes'
address('uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine')
When using crypto constructor function:
import { crypto } from '@waves/ts-lib-crypto'
const { address } = crypto({ output: 'Bytes' })
address('uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine')
Seed generation
The seed is a set of words or bytes that private and public keys are generated from. The usual Waves seed looks like:
uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine
There are couple ways to generate seed:
const handWrittenSeedString = 'uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine'
const handWrittenSeedBytes = [117, 110, 99, 108, 101, 32, 112, 117, 115, 104, 32, 104, 117, 109, 97, 110, 32, 98, 117, 115, 32, 101, 99, 104, 111, 32, 100, 114, 97, 115, 116, 105, 99, 32, 103, 97, 114, 100, 101, 110, 32, 106, 111, 107, 101, 32, 115, 97, 110, 100, 32, 119, 97, 114, 102, 97, 114, 101, 32, 115, 101, 110, 116, 101, 110, 99, 101, 32, 102, 111, 115, 115, 105, 108, 32, 116, 105, 116, 108, 101, 32, 99, 111, 108, 111, 114, 32, 99, 111, 109, 98, 105, 110, 101]
Or if you need seed with nonce:
import { seedWithNonce, randomSeed, address } from '@waves/ts-lib-crypto'
const nonce = 1
const seedphrase = randomSeed()
const seed = seedWithNonce(seedphrase, nonce)
address(seed)
Seed could be any string or number[] or Uint8Array or ISeedWithNonce.
There is also a way to generate seed-phrase using ts-lib-crypto described in the next section.
randomSeed
import { randomSeed } from '@waves/ts-lib-crypto'
randomSeed()
You can also specify seed-phrase size:
randomSeed(3)
The default seed size is 15 words.
seedWordsList
If you want to get all the valid seed words that official waves-client generates seed-phrase from, use seedWordsList the 2048 word array.
import { seedWordsList } from '@waves/ts-lib-crypto'
console.log(seedWordsList)
Keys and address
publicKey
You could get public key either from raw seed-phrase or seed with nonce:
import { publicKey, seedWithNonce } from '@waves/ts-lib-crypto'
const seed = 'uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine'
publicKey(seed)
publicKey(seedWithNonce(seed, 0))
Or even from private key, it's usefull in some cases:
import { publicKey, privateKey, seedWithNonce } from '@waves/ts-lib-crypto'
const seed = 'uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine'
const pk = privateKey(seed)
publicKey({ privateKey: pk })
privateKey
Same with private key:
import { privateKey, seedWithNonce } from '@waves/ts-lib-crypto'
const seed = 'uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine'
privateKey(seed)
privateKey(seedWithNonce(seed, 99))
keyPair
You could also obtain a keyPair:
import { keyPair } from '@waves/ts-lib-crypto'
const seed = 'uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine'
keyPair(seed)
address
You can create an address for Mainnet:
import { address } from '@waves/ts-lib-crypto'
const seed = 'uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine'
address(seed)
or Testnet:
address(seed, 'T')
alternatively You could use TEST_NET_CHAIN_ID constant instead of T literal like this:
import { address, TEST_NET_CHAIN_ID } from '@waves/ts-lib-crypto'
const seed = 'uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine'
address(seed, TEST_NET_CHAIN_ID)
There are several more useful constants, you can check them in [constants] section.
Signatures
signBytes
To sign arbitrary bytes or usually transaction bytes you should use the signBytes function.
Here is sign with seed example:
import { signBytes } from '@waves/ts-lib-crypto'
const bytes = [117, 110, 99, 108, 101]
const seed = 'uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine'
signBytes(seed, bytes)
Also you can use private key to sign bytes:
import { signBytes, privateKey } from '@waves/ts-lib-crypto'
const bytes = [117, 110, 99, 108, 101]
const seed = 'uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine'
const key = privateKey(seed)
signBytes({ privateKey: key }, bytes)
Remember that you can use base58 strings when it's about binary data, so you can represent bytes as base58 too:
signBytes({ privateKey: key }, 'Fk1sjwdPSwZ4bPwvpCGPH6')
You can learn more about it in the outputs section.
verifySignature
Verifying signature is a way to proof what particular bytes was signed with a particular private key or seed which correspond to public key that we are checking against:
import { signBytes, verifySignature, keyPair } from '@waves/ts-lib-crypto'
const bytes = [117, 110, 99, 108, 101]
const seed = 'uncle push human bus echo drastic garden joke sand warfare sentence fossil title color combine'
const keys = keyPair(seed)
const signature = signBytes(keys, bytes)
verifySignature(keys.publicKey, bytes, signature)
Hashing
There are three hashing algorithms available in ts-lib-crypto.
blake2b
import { blake2b } from '@waves/ts-lib-crypto'
const bytesArray = [117, 110, 99, 108, 101]
const bytesUint = Uint8Array.from([117, 110, 99, 108, 101])
const bytesBase58 = 'EFRr9cp'
blake2b(bytesArray)
blake2b(bytesUint)
blake2b(bytesBase58)
keccak
import { keccak } from '@waves/ts-lib-crypto'
const bytesArray = [117, 110, 99, 108, 101]
const bytesUint = Uint8Array.from([117, 110, 99, 108, 101])
const bytesBase58 = 'EFRr9cp'
keccak(bytesArray)
keccak(bytesUint)
keccak(bytesBase58)
sha256
import { sha256 } from '@waves/ts-lib-crypto'
const bytesArray = [117, 110, 99, 108, 101]
const bytesUint = Uint8Array.from([117, 110, 99, 108, 101])
const bytesBase58 = 'EFRr9cp'
sha256(bytesArray)
sha256(bytesUint)
sha256(bytesBase58)
Random
There is several ways to get random values in ts-lib-crypto.
To get an Uint8Array of random values simply use:
randomBytes
import { randomBytes } from '@waves/ts-lib-crypto'
randomBytes(3)
If you want more control over the values format you could use:
random
import { random } from '@waves/ts-lib-crypto'
const length = 3
random(length, 'Array8')
random(length, 'Array16')
random(length, 'Array32')
random(length, 'Buffer')
random(length, 'Uint8Array')
random(length, 'Uint16Array')
random(length, 'Uint32Array')
Base encoding\decoding
import { base16Encode, base16Decode, base58Encode, base58Decode, base64Encode, base64Decode, randomBytes } from '@waves/ts-lib-crypto'
const bytes = randomBytes(32)
const base16String = base16Encode(bytes)
const bytesFromBase16 = base16Decode(base16String)
const base58String = base58Encode(bytes)
const bytesFromBase58 = base58Decode(base58String)
const base64String = base64Encode(bytes)
const bytesFromBase64 = base64Decode(base64String)
Messaging
These methods implement waves messaging protocol
- sharedKey
- messageDecrypt
- messageEncrypt
import { sharedKey, messageEncrypt, messageDecrypt, keyPair } from '@waves/ts-lib-crypto'
const bobKeyPair = keyPair('Bob')
const aliceKeyPair = keyPair('Alice')
const msg = 'hello world'
const sharedKeyA = sharedKey(aliceKeyPair.privateKey, bobKeyPair.publicKey, 'waves')
const encrypted = messageEncrypt(sharedKeyA, msg)
const sharedKeyB = sharedKey(aliceKeyPair.privateKey, bobKeyPair.publicKey, 'waves')
const decrypted = messageDecrypt(sharedKeyB, encrypted)
Encryption
This is low level functionality where you have to generate key and iv yourself
aesEncrypt
Encrypt bytes using AES algorithm.
import { aesEncrypt, randomBytes } from '@waves/ts-lib-crypto'
const data = Uint8Arraty.from([1,2,3])
const mode = 'CBC'
const key = randomBytes(32)
const iv = randomBytes(32)
const encrypted = aesEncrypt(data, key, mode, iv)
aesDecrypt
Decrypt bytes using AES algorithm
const decrypted = aesDecrypt(encrypted, key, mode, iv)
Seed encryption
These functions implements seed encryption protocol used in DexClient and WavesKeeper
import { encryptSeed, decryptSeed } from '@waves/ts-lib-crypto'
const seed = 'some secret seed phrase i use'
const encrypted = encryptSeed(seed, 'secure password')
const decrypted = decryptSeed(encryptSeed, 'secure password')
Utils
Utility functions designed to help 3rd party developers working with js binary types like Uint8Array and Buffer.
split
You can use split for splitting bytes to sub arrays.
import { split, randomBytes } from '@waves/ts-lib-crypto'
const bytes = randomBytes(2 + 3 + 4 + 10)
split(bytes, 2, 3, 4)
Alternatively, you can use array deconstruction syntax:
const [a, b, c, rest] = split(bytes, 2, 3, 4)
concat
Concat is the opposite and pretty self-explanatory:
import { concat, randomBytes } from '@waves/ts-lib-crypto'
const bytesA = randomBytes(2)
const bytesB = randomBytes(2)
concat(bytesA, bytesB)
stringToBytes
import { stringToBytes } from '@waves/ts-lib-crypto'
stringToBytes('Waves!')
bytesToString
import { bytesToString } from '@waves/ts-lib-crypto'
bytesToString([ 87, 97, 118, 101, 115, 33 ])
Constants
There is several useful constants declared at ts-lib-crypto:
const PUBLIC_KEY_LENGTH = 32
const PRIVATE_KEY_LENGTH = 32
const SIGNATURE_LENGTH = 64
const ADDRESS_LENGTH = 26
const MAIN_NET_CHAIN_ID = 87
const TEST_NET_CHAIN_ID = 84
Interface
The full IWavesCrypto interface can be found on the project`s github in interface.ts.
seedWithNonce: (seed: TSeed, nonce: number) => INonceSeed
keyPair: (seed: TSeed) => TKeyPair<TBytesOrBase58>
publicKey: (seed: TSeed) => TBytesOrBase58
privateKey: (seed: TSeed) => TBytesOrBase58
address: (seedOrPublicKey: TSeed | TPublicKey<TBinaryIn>, chainId?: TChainId) => TBytesOrBase58
signBytes: (seedOrPrivateKey: TSeed | TPrivateKey<TBinaryIn>, bytes: TBinaryIn, random?: TBinaryIn) => TDesiredOut
blake2b: (input: TBinaryIn) => TBytes
keccak: (input: TBinaryIn) => TBytes
sha256: (input: TBinaryIn) => TBytes
base64Encode: (input: TBinaryIn) => TBase64
base64Decode: (input: TBase64) => TBytes
base58Encode: (input: TBinaryIn) => TBase58
base58Decode: (input: TBase58) => TBytes
base16Encode: (input: TBinaryIn) => TBase16
base16Decode: (input: TBase16) => TBytes
stringToBytes: (input: string) => TBytes
bytesToString: (input: TBinaryIn) => string
split: (binary: TBinaryIn, ...sizes: number[]) => TBytes[]
concat: (...binaries: TBinaryIn[]) => TBytes
random<T extends keyof TRandomTypesMap>(count: number, type: T): TRandomTypesMap[T]
randomBytes: (size: number) => TBytes
randomSeed: (wordsCount?: number) => string
verifySignature: (publicKey: TBinaryIn, bytes: TBinaryIn, signature: TBinaryIn) => boolean
verifyPublicKey: (publicKey: TBinaryIn) => boolean
verifyAddress: (address: TBinaryIn, optional?: { chainId?: TChainId, publicKey?: TBinaryIn }) => boolean
sharedKey: (privateKeyFrom: TBinaryIn, publicKeyTo: TBinaryIn, prefix: TRawStringIn) => TBytesOrBase58
messageDecrypt: (sharedKey: TBinaryIn, encryptedMessage: TBinaryIn) => string
messageEncrypt: (sharedKey: TBinaryIn, message: TRawStringIn) => TBytes
aesEncrypt: (data: TRawStringIn, secret: TBinaryIn, mode?: AESMode, iv?: TBinaryIn) => TBytes
aesDecrypt: (encryptedData: TBinaryIn, secret: TBinaryIn, mode?: AESMode, iv?: TBinaryIn) => TBytes
More examples
Every example used in this document and many more can be found on the project`s github inside examples folder.